﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;

namespace Citrix.XaAdminMobile.Util
{
    /// <summary>
    /// This is a branch of the behaviour from the CMP.NET SDK. It has been modified to support logical
    /// scrolling, and has further enhancements to the inertia scrolling.
    /// </remarks>
    internal class KineticScrollingBehaviour
    {
        #region Friction

        /// <summary>
        /// Friction Attached Dependency Property. It controls how much friction there is to
        /// resist inertia scrolling. 1.0 is no friction, and 0.0 is full friction (no inertia
        /// scrolling).
        /// </summary>
        public static readonly DependencyProperty FrictionProperty =
            DependencyProperty.RegisterAttached("Friction", typeof(double), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata((double)0.92));

        /// <summary>
        /// Gets the Friction property.
        /// </summary>
        /// <param name="obj">The dependency object to get the value from.</param>
        /// <returns>The friction.</returns>
        public static double GetFriction(DependencyObject obj)
        {
            return (double)obj.GetValue(FrictionProperty);
        }

        /// <summary>
        /// Sets the Friction property.
        /// </summary>
        /// <param name="obj">The dependency object to set the value on.</param>
        /// <param name="value">The value to set.</param>
        public static void SetFriction(DependencyObject obj, double value)
        {
            obj.SetValue(FrictionProperty, value);
        }

        #endregion

        #region ScrollStartPoint

        /// <summary>
        /// ScrollStartPoint Attached Dependency Property. This is set to the point where the
        /// user first clicks to start drag scrolling. It is used as the initial reference point
        /// to determine how much to scroll when the mouse is moved.
        /// </summary>
        private static readonly DependencyProperty ScrollStartPointProperty =
            DependencyProperty.RegisterAttached("ScrollStartPoint", typeof(Point), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata((Point)new Point()));

        /// <summary>
        /// Gets the ScrollStartPoint property.
        /// </summary>
        private static Point GetScrollStartPoint(DependencyObject d)
        {
            Contract.Requires(d != null);
            return (Point)d.GetValue(ScrollStartPointProperty);
        }

        /// <summary>
        /// Sets the ScrollStartPoint property.
        /// </summary>
        private static void SetScrollStartPoint(DependencyObject d, Point value)
        {
            Contract.Requires(d != null);
            d.SetValue(ScrollStartPointProperty, value);
        }

        #endregion

        #region ScrollStartOffset

        /// <summary>
        /// ScrollStartOffset Attached Dependency Property
        /// </summary>
        private static readonly DependencyProperty ScrollStartOffsetProperty =
            DependencyProperty.RegisterAttached("ScrollStartOffset", typeof(Point), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata((Point)new Point()));

        /// <summary>
        /// Gets the ScrollStartOffset property.
        /// </summary>
        private static Point GetScrollStartOffset(DependencyObject d)
        {
            Contract.Requires(d != null);
            return (Point)d.GetValue(ScrollStartOffsetProperty);
        }

        /// <summary>
        /// Sets the ScrollStartOffset property.
        /// </summary>
        private static void SetScrollStartOffset(DependencyObject d, Point value)
        {
            Contract.Requires(d != null);
            d.SetValue(ScrollStartOffsetProperty, value);
        }

        #endregion

        #region Can initiate dragging flag

        /// <summary>
        /// CanStartDragScrolling Attached Dependency Property. Used to flag that it is
        /// permitted to capture the mouse and initiate drag scrolling. This flag gets set on
        /// mouse down events.
        /// </summary>
        private static readonly DependencyProperty CanStartDragScrollingProperty =
            DependencyProperty.RegisterAttached("CanStartDragScrolling", typeof(bool), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata(false));

        /// <summary>
        /// Gets the CanStartDragScrolling property.
        /// </summary>
        private static bool GetCanStartDragScrolling(DependencyObject d)
        {
            Contract.Requires(d != null);
            return (bool)d.GetValue(CanStartDragScrollingProperty);
        }

        /// <summary>
        /// Sets the CanStartDragScrolling property.
        /// </summary>
        private static void SetCanStartDragScrolling(DependencyObject d, bool value)
        {
            Contract.Requires(d != null);
            d.SetValue(CanStartDragScrollingProperty, value);
        }

        #endregion

        #region InertiaProcessor

        /// <summary>
        /// InertiaProcessor Attached Dependency Property. This is used to attach a helper
        /// object to a ScrollViewer that is used to track inertia and apply inertia scrolling.
        /// </summary>
        private static readonly DependencyProperty InertiaProcessorProperty =
            DependencyProperty.RegisterAttached("InertiaProcessor", typeof(InertiaHandler), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata((InertiaHandler)null));

        /// <summary>
        /// Gets the InertiaProcessor property.
        /// </summary>
        private static InertiaHandler GetInertiaProcessor(DependencyObject d)
        {
            Contract.Requires(d != null);
            return (InertiaHandler)d.GetValue(InertiaProcessorProperty);
        }

        /// <summary>
        /// Sets the InertiaProcessor property.
        /// </summary>
        private static void SetInertiaProcessor(DependencyObject d, InertiaHandler value)
        {
            Contract.Requires(d != null);
            d.SetValue(InertiaProcessorProperty, value);
        }

        #endregion

        #region Scroll Granularity Property

        /// <summary>
        /// The scroll granularity controls the minimum scroll amount when performing scroll
        /// operations. Having a minimum scroll amount prevents very small scrolls from
        /// occurring. This is useful in a TS environment as it effectively reduces the frame-
        /// rate when scrolling, thereby improving scroll performance (since we send less data
        /// down to the client).
        /// 
        /// The tradeoff is the user will get a degree of chunkiness when scrolling since they
        /// cannot do per-pixel (hyper) scrolling, however the overall experience ends up
        /// smoother.
        /// </summary>
        /// <remarks>
        /// Note: The value is in WPF DIUs.
        /// </remarks>
        public static readonly DependencyProperty ScrollGranularityProperty =
            DependencyProperty.RegisterAttached("ScrollGranularity", typeof(double), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata(16.0));

        /// <summary>
        /// Getter for <see cref="ScrollGranularityProperty"/>.
        /// </summary>
        /// <param name="obj">The dependency object to get the property value for.</param>
        /// <returns>The property value.</returns>
        public static double GetScrollGranularity(DependencyObject obj)
        {
            return (double)obj.GetValue(ScrollGranularityProperty);
        }

        /// <summary>
        /// Setter for <see cref="ScrollGranularityProperty"/>.
        /// </summary>
        /// <param name="obj">The dependency object to set the property value for.</param>
        /// <param name="value">The value for the property.</param>
        public static void SetScrollGranularity(DependencyObject obj, double value)
        {
            obj.SetValue(ScrollGranularityProperty, value);
        }

        #endregion

        #region HandleKineticScrolling

        /// <summary>
        /// HandleKineticScrolling Attached Dependency Property. Use this property to enable
        /// and disable kinetic scrolling.
        /// </summary>
        public static readonly DependencyProperty HandleKineticScrollingProperty =
            DependencyProperty.RegisterAttached("HandleKineticScrolling", typeof(bool),
            typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata((bool)false,
                    new PropertyChangedCallback(OnHandleKineticScrollingChanged)));

        /// <summary>
        /// Gets the HandleKineticScrolling property.
        /// </summary>
        /// <param name="obj">The dependency object to get the value from.</param>
        /// <returns>True kinetic scrolling is enabled, false if disabled.</returns>
        public static bool GetHandleKineticScrolling(DependencyObject obj)
        {
            return (bool)obj.GetValue(HandleKineticScrollingProperty);
        }

        /// <summary>
        /// Sets the HandleKineticScrolling property.
        /// </summary>
        /// <param name="obj">The dependency object to set the value on.</param>
        /// <param name="value">The value to set.</param>
        public static void SetHandleKineticScrolling(DependencyObject obj, bool value)
        {
            obj.SetValue(HandleKineticScrollingProperty, value);
        }

        /// <summary>
        /// Handles changes to the HandleKineticScrolling property.
        /// </summary>
        private static void OnHandleKineticScrollingChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            ScrollViewer scroller = d as ScrollViewer;
            if (null != scroller)
            {
                if ((bool)e.NewValue)
                {
                    scroller.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown;
                    scroller.PreviewMouseMove += OnPreviewMouseMove;
                    scroller.PreviewMouseLeftButtonUp += OnPreviewMouseLeftButtonUp;
                    SetInertiaProcessor(scroller, new InertiaHandler(scroller));
                }
                else
                {
                    scroller.PreviewMouseLeftButtonDown -= OnPreviewMouseLeftButtonDown;
                    scroller.PreviewMouseMove -= OnPreviewMouseMove;
                    scroller.PreviewMouseLeftButtonUp -= OnPreviewMouseLeftButtonUp;
                    var inertia = GetInertiaProcessor(scroller);
                    if (inertia != null)
                    {
                        inertia.Dispose();
                    }
                    scroller.ClearValue(InertiaProcessorProperty);
                    scroller.ClearValue(CanStartDragScrollingProperty);
                    scroller.ClearValue(ScrollStartPointProperty);
                    scroller.ClearValue(ScrollStartOffsetProperty);
                    scroller.ClearValue(IsMouseCapturedProperty);
                }
            }
        }

        /// <summary>
        /// Used as a work around for a stack overflow. There are cases where the mouse capture
        /// in OnPreviewMouseMove triggers another call to OnPreviewMouseMove, however
        /// ScrollViewer.IsMouseCaptured still returns false. Hence we end up in an infinite
        /// recursion. By manually tracking mouse capture we prevent this infinite recursion.
        /// </summary>
        private static readonly DependencyProperty IsMouseCapturedProperty =
            DependencyProperty.RegisterAttached("IsMouseCaptured", typeof(bool), typeof(KineticScrollingBehaviour),
                new FrameworkPropertyMetadata(false));

        /// <summary>
        /// Gets the IsMouseCaptured property.
        /// </summary>
        private static bool GetIsMouseCaptured(DependencyObject d)
        {
            Contract.Requires(d != null);
            return (bool)d.GetValue(IsMouseCapturedProperty);
        }

        /// <summary>
        /// Sets the IsMouseCaptured property.
        /// </summary>
        private static void SetIsMouseCaptured(DependencyObject d, bool value)
        {
            Contract.Requires(d != null);
            d.SetValue(IsMouseCapturedProperty, value);
        }

        #endregion

        #region Mouse Events

        /// <summary>
        /// Handles PreviewMouseLeftButtonDown. We set our initial state in preparation of
        /// beginning drag scrolling. Drag scrolling gets initiated by a followup mouse move
        /// event.
        /// </summary>
        private static void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Contract.Requires(sender != null);
            Contract.Requires(e != null);

            var scrollViewer = (ScrollViewer)sender;

            // Reset mouse capture in case some other element has stolen capture and our normal
            // release logic on preview mouse button up doesn't get run.
            KineticScrollingBehaviour.SetIsMouseCaptured(scrollViewer, false);

            if (scrollViewer.IsMouseOver)
            {
                var inertiaProcessor = GetInertiaProcessor(scrollViewer);

                // If we are currently inertia scrolling, stop it so the scroll viewer snaps
                // back onto the mouse for drag scrolling. If the mouse down was used to stop
                // inertia, we consume the mouse event to stop list box selection or other
                // scroll viewer from processing it.
                if (inertiaProcessor.IsInertiaScrolling)
                {
                    inertiaProcessor.StopInertiaScroll();
                    e.Handled = true;
                }

                // Save starting point, used later when determining how much to scroll.
                var pos = e.GetPosition(scrollViewer);
                SetScrollStartPoint(scrollViewer, pos);
                SetScrollStartOffset(scrollViewer, new
                    Point(scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset));

                // Flag that we got a mouse down to initiate a JIT mouse capture to start drag
                // scrolling in a followup mouse move event.
                KineticScrollingBehaviour.SetCanStartDragScrolling(scrollViewer, true);
            }
        }

        /// <summary>
        /// Handles PreviewMouseMove. This handler both initiates drag scrolling and
        /// executes drag scrolling behaviour.
        /// 
        /// If the user has previously clicked left mouse, held it down and started a mouse move
        /// we will capture the mouse to initiate a drag scroll. Since we only capture the mouse
        /// for this exact sequence, users can still click and release in the one spot without
        /// moving the mouse to perform mouse clicks on elements in the scrollable area.
        /// </summary>
        private static void OnPreviewMouseMove(object sender, MouseEventArgs e)
        {
            Contract.Requires(sender != null);
            Contract.Requires(e != null);

            var scrollViewer = (ScrollViewer)sender;

            //
            // Initiate drag scrolling. We do a JIT mouse capture.
            //
            if (KineticScrollingBehaviour.GetCanStartDragScrolling(scrollViewer))
            {
                // Clear start drag flag if the mouse button was released.
                if (Mouse.LeftButton != MouseButtonState.Pressed)
                {
                    KineticScrollingBehaviour.SetCanStartDragScrolling(scrollViewer, false);
                }

                // We only capture the mouse if the user has held the mouse down from a previous
                // click and started moving the mouse.
                if (Mouse.LeftButton == MouseButtonState.Pressed &&
                    !scrollViewer.IsMouseCaptured && !KineticScrollingBehaviour.GetIsMouseCaptured(scrollViewer) &&
                    DraggedEnoughToStartScrolling(GetScrollStartPoint(scrollViewer), e.GetPosition(scrollViewer), KineticScrollingBehaviour.GetScrollGranularity(scrollViewer)))
                {
                    // Also manually track mouse capture to prevent infinite recursion that
                    // can otherwise occur in certain cases.
                    KineticScrollingBehaviour.SetIsMouseCaptured(scrollViewer, true);

                    scrollViewer.CaptureMouse();
                }
            }

            //
            // Perform drag scrolling.
            //
            if (scrollViewer.IsMouseCaptured)
            {
                var inertiaProcessor = GetInertiaProcessor(scrollViewer);
                Point currentPoint = e.GetPosition(scrollViewer);
                var scrollStartPoint = GetScrollStartPoint(scrollViewer);
                Point scrollTarget;

                // Determine the new target scroll position relative to our original starting
                // point.
                Point physicalDelta = new Point(scrollStartPoint.X - currentPoint.X,
                    scrollStartPoint.Y - currentPoint.Y);

                // The start offset can be physical or logical depending on whether content
                // scrolling is enabled.
                var scrollStartOffset = GetScrollStartOffset(scrollViewer);

                if (scrollViewer.CanContentScroll)
                {
                    Size scrollerSize = new Size(scrollViewer.ActualWidth, scrollViewer.ActualHeight);
                    Size averageItemSize = new Size(scrollerSize.Width / scrollViewer.ViewportWidth, scrollerSize.Height / scrollViewer.ViewportHeight);
                    Point logicalDelta = new Point(physicalDelta.X / averageItemSize.Width, physicalDelta.Y / averageItemSize.Height);

                    // Now get the target position as an absolute scroll position.
                    scrollTarget = new Point(scrollStartOffset.X + logicalDelta.X,
                        scrollStartOffset.Y + logicalDelta.Y);

                    // Scroll to the new position.
                    if (!double.IsNaN(scrollTarget.X))
                    {
                        scrollViewer.ScrollToHorizontalOffset(scrollTarget.X);
                    }
                    if (!double.IsNaN(scrollTarget.Y))
                    {
                        scrollViewer.ScrollToVerticalOffset(scrollTarget.Y);
                    }
                }
                else
                {
                    // Now get the target position as an absolute scroll position.
                    scrollTarget = new Point(scrollStartOffset.X + physicalDelta.X,
                        scrollStartOffset.Y + physicalDelta.Y);

                    // Get values to consume scroll granularity.
                    double minScrollAmount = KineticScrollingBehaviour.GetScrollGranularity(scrollViewer);
                    Point deltaFromCurrentPosition = new Point(
                        scrollTarget.X - scrollViewer.HorizontalOffset,
                        scrollTarget.Y - scrollViewer.VerticalOffset);

                    // Scroll to the new position, respecting the scroll granularity, i.e. ignore
                    // small movements.
                    if (Math.Abs(deltaFromCurrentPosition.X) >= minScrollAmount)
                    {
                        scrollViewer.ScrollToHorizontalOffset(scrollTarget.X);
                    }
                    if (Math.Abs(deltaFromCurrentPosition.Y) >= minScrollAmount)
                    {
                        scrollViewer.ScrollToVerticalOffset(scrollTarget.Y);
                    }
                }

                // Update current scroll target and velocity in the inertia handler.
                if (inertiaProcessor != null)
                {
                    inertiaProcessor.SetVelocity(currentPoint);
                    inertiaProcessor.ScrollTarget = scrollTarget;
                }


                // Stop other elements stealing mouse capture from us.
                e.Handled = true;
            }
        }

        /// <summary>
        /// Since touch input is not precise, it is common for a tap to click a button to
        /// actually contain some mouse move events between the click down and release events.
        /// We do not want to actually capture the mouse and start drag scrolling unless the
        /// user has dragged a certain amount. This function hence checks the distance from the
        /// starting point to see if we should start scrolling.
        /// </summary>
        /// <param name="start">The start point where the user first tapped.</param>
        /// <param name="current">The current mouse position.</param>
        /// <param name="scrollGranularity">The current scroll granularity. We don't capture the
        /// mouse and start drag scrolling until the user drags by this amount.</param>
        /// <returns>True to start drag scrolling, false not to.</returns>
        private static bool DraggedEnoughToStartScrolling(Point start, Point current, double scrollGranularity)
        {
            Vector vector = current - start;
            return Math.Abs(vector.Length) > scrollGranularity;
        }

        /// <summary>
        /// Handles PreviewMouseLeftButtonUp. We release mouse capture to stop drag scrolling
        /// and initiate inertia scrolling.
        /// </summary>
        private static void OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            Contract.Requires(sender != null);
            Contract.Requires(e != null);

            var scrollViewer = (ScrollViewer)sender;
            if (scrollViewer.IsMouseCaptured)
            {
                var inertiaProcessor = GetInertiaProcessor(scrollViewer);
                Point currentPoint = e.GetPosition(scrollViewer);

                KineticScrollingBehaviour.SetIsMouseCaptured(scrollViewer, false);
                scrollViewer.ReleaseMouseCapture();

                if (inertiaProcessor != null)
                {
                    inertiaProcessor.StartInertiaScroll(currentPoint);
                }

                e.Handled = true;
            }
            KineticScrollingBehaviour.SetCanStartDragScrolling(scrollViewer, false);
        }

        #endregion

        #region Inertia Stuff

        /// <summary>
        /// Handles the inertia. We create an instance of this class for every ScrollViewer the
        /// KineticBehaviour property is attached to. When the user releases the mouse button to
        /// release mouse capture and hence stop drag scrolling, class will apply inertia that
        /// keeps the ScrollViewer scrolling based on velocity and configured friction.
        /// 
        /// We use a timer to perform the inertia scrolling. The timer only remains active while
        /// automated inertia scrolling is needed. As soon as the inertia runs out or the scroll
        /// limits are hit the timer is stopped.
        /// </summary>
        class InertiaHandler : IDisposable
        {
            /// <summary>
            /// The is the maximum time limit for the range of coordinates we track in
            /// <paramref name="swipeCoords"/>.
            /// </summary>
            private const int MaxSwipeTrackDurationMs = 100;

            /// <summary>
            /// This is the time interval in ms over which the velocity is consumed, therefore
            /// we define our velocity according to this adjusted scaled (i.e. Distance / 50ms)
            /// </summary>
            private const int VelocityTimeIntervalMs = 50;

            /// <summary>
            /// Used to track time to determine velocity.
            /// </summary>
            private readonly Stopwatch _stopWatch;

            /// <summary>
            /// Tracks the coordinates of a user swipe, mapped to the elapsed time since the
            /// start of the swipe (in ms). This is used to generate a running average of the
            /// velocity in order to get "smoother" velocity values. Without an average users'
            /// can occasionally spike the velocity by accident and make their content scroll
            /// super fast.
            /// 
            /// We track the last "MaxSwipeTrackDurationMs" worth of coordinates, since we want
            /// a recent average (not the average over the length of the entire swipe
            /// operation).
            /// </summary>
            private readonly List<Tuple<Point, long>> _swipeCoords = new List<Tuple<Point, long>>();

            /// <summary>
            /// The current velocity of the scrollable area. This is set based on the speed the
            /// user is moving the mouse while drag scrolling. Once the user releases the mouse
            /// and inertia scrolling starts we reduce this value based on the configured
            /// friction.
            /// </summary>
            /// <remarks>
            /// The velocity is stored as DIUs / (VelocityTimeIntervalMs)ms. We use per
            /// VelocityTimeIntervalMs as that is the frequency of the timer we use to perform
            /// auto scrolling due to inertia.
            /// 
            /// We always track velocity in physical units. We do a JIT conversion to logical
            /// units when we are using logical (content) scrolling.
            /// </remarks>
            private Vector _velocity;

            /// <summary>
            /// A reference back to the scroll viewer we giving scrolling inertia to.
            /// </summary>
            private readonly ScrollViewer _scroller;

            /// <summary>
            /// This timer is used to auto scroll to simulate the inertia. It needs to be
            /// started using StartInertiaScroll when the scroll viewer releases mouse capture.
            /// </summary>
            private readonly DispatcherTimer _animationTimer;

            /// <summary>
            /// This is the position we are scrolling to. While the user is drag scrolling this
            /// is set based on mouse move events. Once the user releases the mouse and we begin
            /// inertia scrolling we set this based on our velocity.
            /// 
            /// Note: the scroll target can contain physical or logical values (or both),
            /// depending on whether the scroll viewer has content scrolling enabled.
            /// </summary>
            private Point _scrollTarget;

            /// <summary>
            /// Holds the last scroll position the scroll viewer scrolled to.
            /// </summary>
            private Point _lastInertiaScrollPosition;

            /// <summary>
            /// Used to apply scroll granularity to kinetic scrolling. This value tracks the
            /// last position our auto-velocity scrolling move the scroll view to.
            /// </summary>
            private Point _lastAppliedScrollTarget;

            /// <summary>
            /// This is the current scroll granularity for the scroller. We have to ensure we
            /// only ever scroll by at least this amount.
            /// </summary>
            private double _minScrollAmount = 0;

            /// <summary>
            /// Used to check whether we are performing the first scroll operation in an
            /// automated inertia scroll after the user releases the mouse.
            /// </summary>
            private bool _firstInertiaScroll = true;

            /// <summary>
            /// Used to check whether we need to update the last scroll position. We track the
            /// position while inertia scrolling to detect other callers manually setting the
            /// scroll position. When this occurs we cancel inertia scrolling.
            /// </summary>
            private bool _updateLastScrollPosition = false;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="scroller">The ScrollViewer we are associated with.</param>
            public InertiaHandler(ScrollViewer scroller)
            {
                Contract.Requires(scroller != null);

                this._scroller = scroller;
                this._scroller.ScrollChanged += new ScrollChangedEventHandler(scroller_ScrollChanged);
                this._animationTimer = new DispatcherTimer();
                this._animationTimer.Interval = new TimeSpan(0, 0, 0, 0, VelocityTimeIntervalMs);
                this._animationTimer.Tick += new EventHandler(HandleWorldTimerTick);
                this._lastInertiaScrollPosition = new Point();

                this._stopWatch = new Stopwatch();
            }

            /// <summary>
            /// Used to set the target position the user is trying to scroll to using drag
            /// scrolling. When the user moves the mouse quickly the scrolling will lag slightly
            /// behind the mouse position. So when we initiate drag scrolling we make sure we
            /// first respect this target value the user was trying to achieve by scrolling to
            /// it first. We then apply our velocity/inertia to scroll beyond this position.
            /// 
            /// This property should be set on every mouse move event while drag scrolling.
            /// </summary>
            /// <remarks>
            /// This can either be a physical or logical scroll position depending on whether
            /// content scrolling is enabled.
            /// </remarks>
            public Point ScrollTarget
            {
                get { return this._scrollTarget; }
                set
                {
                    this._scrollTarget = this._lastAppliedScrollTarget = value;
                    this._firstInertiaScroll = true;
                }
            }

            /// <summary>
            /// Called by the mouse move handler to update the current velocity of the scroll
            /// viewer scrolling. The caller supplies only the current physical position of the
            /// user's finger. We maintain a time indexed history in order to calculate the
            /// resulting velocity.
            /// </summary>
            /// <param name="currentPoint">The current physical position of the user's finger.</param>
            public void SetVelocity(Point currentPoint)
            {
                var elapsedSinceSwipeStartedMs = this._stopWatch.ElapsedMilliseconds;

                if (this._swipeCoords.Count > 0)
                {
                    var oldest = this._swipeCoords[0];

                    // Prune the queue of coordinates based on elapsed time. We only keep the
                    // last 100ms worth of coordinates.
                    if (this._swipeCoords.Count > 1)
                    {
                        var newest = this._swipeCoords[this._swipeCoords.Count - 1];
                        long pruningDiff = newest.Item2 - oldest.Item2;
                        if (pruningDiff > MaxSwipeTrackDurationMs)
                        {
                            // Exclude the last item, we always leave one item in the queue.
                            int index;
                            for (index = 0; index < this._swipeCoords.Count - 1; ++index)
                            {
                                var current = this._swipeCoords[index];
                                pruningDiff = newest.Item2 - current.Item2;
                                if (pruningDiff <= MaxSwipeTrackDurationMs)
                                {
                                    break;
                                }
                            }
                            this._swipeCoords.RemoveRange(0, index + 1);
                        }
                    }

                    //
                    // Add a new coordinate, and update the velocity.
                    //
                    var added = new Tuple<Point, long>(currentPoint, elapsedSinceSwipeStartedMs);
                    this._swipeCoords.Add(added);

                    var diffTimeMs = added.Item2 - oldest.Item2;
                    Vector diffDistance = oldest.Item1 - added.Item1;

                    // Calculate velocity per (VelocityTimeIntervalMs)ms
                    this._velocity = diffDistance * VelocityTimeIntervalMs / diffTimeMs;
                }
                else
                {
                    this._swipeCoords.Add(new Tuple<Point, long>(currentPoint, 0));
                    this._velocity.X = 0;
                    this._velocity.Y = 0;
                    this._stopWatch.Restart();
                }
            }

            /// <summary>
            /// Called to start inertia scrolling when the scroll viewer releases mouse capture.
            /// </summary>
            public void StartInertiaScroll(Point releasePoint)
            {
                var elapsedSinceSwipeStartedMs = this._stopWatch.ElapsedMilliseconds;
                var previous = this._swipeCoords.LastOrDefault();

                if (previous != null)
                {
                    var diffTimeMs = elapsedSinceSwipeStartedMs - previous.Item2;

                    if (diffTimeMs > MaxSwipeTrackDurationMs)
                    {
                        this._velocity.X = 0;
                        this._velocity.Y = 0;
                    }
                    else
                    {
                        SetVelocity(releasePoint);
                    }
                }

                this._animationTimer.Start();

                // When we start inertia scrolling, ensure we have the most up to date scroll
                // granularity.
                this._minScrollAmount = KineticScrollingBehaviour.GetScrollGranularity(this._scroller);
            }

            /// <summary>
            /// Get whether inertia scrollig is running.
            /// </summary>
            public bool IsInertiaScrolling
            {
                get
                {
                    return this._animationTimer.IsEnabled;
                }
            }

            /// <summary>
            /// Called to stop inertia scrolling when the user re-clicks on the scroll viewer.
            /// </summary>
            public void StopInertiaScroll()
            {
                this._animationTimer.Stop();
                this._swipeCoords.Clear();
                this._stopWatch.Reset();
            }

            /// <summary>
            /// We handle scroll changed to track the current scroll position while inertia
            /// scrolling. We use the current position to detect other callers setting the
            /// scroll position.
            /// </summary>
            private void scroller_ScrollChanged(object sender, ScrollChangedEventArgs e)
            {
                if (this._updateLastScrollPosition)
                {
                    this._updateLastScrollPosition = false;
                    this._lastInertiaScrollPosition.X = e.HorizontalOffset;
                    this._lastInertiaScrollPosition.Y = e.VerticalOffset;
                }
            }

            /// <summary>
            /// Timer handler that applies inertia scrolling.
            /// </summary>
            /// <param name="sender">Not used.</param>
            /// <param name="e">Not used.</param>
            private void HandleWorldTimerTick(object sender, EventArgs e)
            {
                try
                {
                    //
                    // Check if something else has set the scroll position of the scroll viewer.
                    // If so this cancels inertia scrolling. Note: the timer can tick more often
                    // than the scroll position is set. So we have to guard when we make our
                    // check.
                    //
                    if (!this._firstInertiaScroll && !this._updateLastScrollPosition &&
                        (this._scroller.HorizontalOffset != this._lastInertiaScrollPosition.X ||
                        this._scroller.VerticalOffset != this._lastInertiaScrollPosition.Y))
                    {
                        this._velocity.X = 0;
                        this._velocity.Y = 0;
                        this._animationTimer.Stop();
                        this._swipeCoords.Clear();
                        this._stopWatch.Reset();
                        return;
                    }

                    //
                    // We are no longer drag scrolling, hence apply inertia to keep the
                    // ScrollViewer scrolling automatically.
                    //
                    if (CanScroll(this._scrollTarget.X, this._lastAppliedScrollTarget.X))
                    {
                        this._firstInertiaScroll = false;
                        this._updateLastScrollPosition = true;
                        this._scroller.ScrollToHorizontalOffset(this._scrollTarget.X);
                        this._lastAppliedScrollTarget.X = this._scrollTarget.X;
                    }
                    if (CanScroll(this._scrollTarget.Y, this._lastAppliedScrollTarget.Y))
                    {
                        this._firstInertiaScroll = false;
                        this._updateLastScrollPosition = true;
                        this._scroller.ScrollToVerticalOffset(this._scrollTarget.Y);
                        this._lastAppliedScrollTarget.Y = this._scrollTarget.Y;
                    }

                    //
                    // Now apply the velocity to set the position for the next timer tick.
                    //
                    if (this._scroller.CanContentScroll)
                    {
                        Size scrollerSize = new Size(this._scroller.ActualWidth, this._scroller.ActualHeight);
                        Size averageItemSize = new Size(scrollerSize.Width / this._scroller.ViewportWidth, scrollerSize.Height / this._scroller.ViewportHeight);
                        Point logicalDelta = new Point(_velocity.X / averageItemSize.Width, _velocity.Y / averageItemSize.Height);

                        this._scrollTarget.X += logicalDelta.X;
                        this._scrollTarget.Y += logicalDelta.Y;
                    }
                    else
                    {
                        this._scrollTarget.X += _velocity.X;
                        this._scrollTarget.Y += _velocity.Y;
                    }

                    // Apply friction to slow the velocity of inertia scrolling.
                    this._velocity *= KineticScrollingBehaviour.GetFriction(this._scroller);

                    //
                    // Stop the inertia timer if there is no further scrolling required in both
                    // the X and Y dimensions, or the velocity has dropped below a minimum threshold
                    //
                    bool stopHorizontalScroll = false;
                    if (((this._scrollTarget.X > 0) && (this._scroller.HorizontalOffset >= this._scroller.ScrollableWidth)) ||
                        ((this._scrollTarget.X < 0) && (this._scroller.HorizontalOffset <= 0)) ||
                        Math.Abs(this._velocity.X) <= 3)
                    {
                        stopHorizontalScroll = true;
                    }
                    bool stopVerticalScroll = false;
                    if (((this._scrollTarget.Y > 0) && (this._scroller.VerticalOffset >= this._scroller.ScrollableHeight)) ||
                        ((this._scrollTarget.Y < 0) && (this._scroller.VerticalOffset <= 0)) ||
                        Math.Abs(this._velocity.Y) <= 3)
                    {
                        stopVerticalScroll = true;
                    }
                    if (stopHorizontalScroll && stopVerticalScroll)
                    {
                        this._animationTimer.Stop();
                    }
                }
                catch (ArgumentOutOfRangeException)
                {
                    // Seeing instances of scroll target becoming NaN which causes the scroll
                    // operations to fail. Tried using a guard at the top of the function but
                    // that didn't work - not sure why, all updates should be occurring in the
                    // UI thread.
                    this._animationTimer.Stop();
                }
            }

            /// <summary>
            /// This function implements our minimum scroll interval when performing physical
            /// scrolling. If the scroll amount is too low then it returns false.
            /// </summary>
            /// <param name="target">The current target scroll position.</param>
            /// <param name="lastTarget">The last scroll position we set.</param>
            /// <returns>True if we allow the scroll operation or false if it is too small of a
            /// scroll to be worth doing.</returns>
            private bool CanScroll(double target, double lastTarget)
            {
                return _scroller.CanContentScroll || (Math.Abs(target - lastTarget) >= this._minScrollAmount);
            }

            #region IDisposable Members

            /// <summary>
            /// IDisposable implementation.
            /// </summary>
            public void Dispose()
            {
                this._scroller.ScrollChanged -= scroller_ScrollChanged;
                this._animationTimer.Stop();
                this._stopWatch.Stop();
            }

            #endregion
        }

        #endregion
    }
}
